import { Webform } from './webform';
import { FarrisDesignBaseComponent } from '../base-component/component';
import { FarrisDesignBaseNestedComponent } from '../base-component/nested-component';
import { BuilderOptions } from '../entity/builder-options';
import { ComponentSchema } from '../entity/builder-schema';
import { BuilderHTMLElement } from '../entity/builder-element';
import Templates from './templates/templates';
import dragula from '@farris/designer-dragula';
import { findIndex } from 'lodash-es';



export class WebformBuilder extends FarrisDesignBaseComponent {

    public webform: Webform;
    /** 设计器外层dom节点 */
    public element: HTMLElement;

    public dragula: any;


    constructor(element: HTMLElement, options: BuilderOptions) {
        super(null, options);

        this.element = element;
        this.options.hooks = this.options.hooks || {};

        this.options.hooks.renderComponent = this.renderComponent.bind(this);
        this.options.hooks.renderComponents = this.renderComponents.bind(this);

        this.options.hooks.attachComponents = this.attachComponents.bind(this);
        this.options.hooks.attachComponent = this.attachComponent.bind(this);

        // 创建WebForm实例
        this.webform = this.webform || this.createForm(this.options);
    }

    getSchema(): any {
        return this.webform.getSchema();
    }

    /**
     * 创建WebForm实例
     * @param options 创建参数
     */
    createForm(options: any): any {
        this.webform = new Webform(this.element, options);
        if (this.element) {
            this.loadRefs(this.element, {
                form: 'single'
            });
            if (this.refs.form) {
                this.webform.element = this.refs.form;
            }
        }

        return this.webform;
    }


    init() {
        if (this.webform) {
            this.webform.init();
        }
        return super.init();
    }

    get form(): any {
        return this.webform.form;
    }
    set form(value: any) {
        this.setForm(value);
    }

    /**
     * 渲染表单
     * @param form 配置数据,form.contents为json schema数据
     */
    setForm(form: any): any {
        if (!form.contents) {
            form.contents = [];
        }

        if (this.webform) {
            this.webform._form = form || { contents: [] };
            return this.rebuild().then(() => {
                // this.webform.onChange({});

                // 追加渲染结束标志
                this.webform.formReadyResolve();
                return this.form;
            });
        }
    }

    redraw(): any {
        // call方法的第一个参数是this，所以调用的webForm.redraw方法内部的this指向webFormBuilder
        return Webform.prototype.redraw.call(this);
    }


    /**
     * Called when everything is ready.
     *
     * @returns - Wait for webform to be ready.
     */
    getReady(): any {
        return this.webform.getReady();
    }
    /**
     * 渲染外层容器，原formio中builder包含侧边栏和表单左右两个区域，这里只保留form区域
     */
    render(): any {
        return this.renderTemplate('builder', {
            form: this.webform.render(),
        });
    }
    /**
     * 渲染单个控件的builderComponet层级，目的是支持控件右上角的图标按钮
     * @param html 组件html片段
     * @param param1 component：组件JSON schema；self：组件实例
     * @returns 组件外层builder-component节点，ref='dragComponent'
     */
    private renderComponent(html: string, { component, self }): string {
        if (self.type === 'form' && !self.key) {
            return html;
        }

        if (self.parent.noDragDrop) {
            return html;
        }

        const renderResult = this.renderTemplate('builderComponent', {
            html,
            childComponent: component
        });
        return renderResult;

    }

    /**
     * 渲染子组件的集合节点html。
     * 为了方便拖拽，容器内所有的子组件都放在一个父div下。
     * 若容器内没有子组件，则展示提示信息条
     * @param html 子组件html片段
     * @param param1 components: 容器内包含的子组件实例集合；self:容器组件的实例
     * @returns 组件集合节点html片段 class:builder-components drag-container
     */
    private renderComponents(html: string, { components, self }): string {
        if (self.type === 'datagrid' && components.length > 0 || self.noDragDrop) {
            return html;
        }

        if (!components || (!components.length && !components.nodrop) ||
            (self.type === 'form' && components.length <= 1 && (components.length === 0 || components[0].type === 'button'))
        ) {
            html = this.renderTemplate('builderPlaceholder', { position: 0 }) + html;
        }

        const renderResult = this.renderTemplate('builderComponents', {
            key: self.key,
            type: self.type,
            html
        });
        return renderResult;
    }


    attach(element: HTMLElement): Promise<any> {
        return super.attach(element).then(() => {
            this.loadRefs(element, {
                form: 'single'
            });

            this.initDragula();

            // refs.form 是表单最外层DOM容器
            if (this.refs.form) {
                return this.webform.attach(this.refs.form);
            }
        });
    }

    /**
     * 子组件JSON结构和当前组件的实例添加到DOM中并注册拖拽容器。节点class= 'builder-components...'
     * @param element dom元素
     * @param childrenComponents 容器内的子组件实例集合
     * @param childrenContents 子组件JSON schema集合
     * @param component 容器组件实例
     * @returns 容器类组件的子组件集合
     */
    private attachComponents(element: HTMLElement, childrenComponents: FarrisDesignBaseComponent[], childrenContents: any[], component: Webform) {

        // don't attach if no element was found or component doesn't participate in drag'n'drop.
        if (!element) {
            return;
        }
        if (component.noDragDrop) {
            return element;
        }
        // 获取容器中的子组件集合节点
        const containerElement: BuilderHTMLElement = element.querySelector(`[dragref='${component.component.id}-container']`) || element;

        // 将子组件JSON结构和当前组件的实例添加到DOM中，方便后续调用
        containerElement.childrenContents = childrenContents;
        containerElement.componentInstance = component;

        // 将容器添加到拖拽列表中，dragula控件会监听容器中元素的拖动事件
        if (this.dragula && containerElement) {
            // containerElement 为页面中的容器节点的builder-components层级
            this.dragula.containers.push(containerElement);
        }

        // 返回容器的子节点集合，即为ref='component'的节点集合
        return element.children;
    }

    /**
     * 附加组件实例，注册图标事件
     * @param element 组件dom元素
     * @param component 组件实例，如ButtonComponent
     * @returns 组件dom元素
     */
    private attachComponent(element: BuilderHTMLElement, componentInstance: any): BuilderHTMLElement {
        // 将组件实例附加到DOM元素上，方便后续调用
        element.componentInstance = componentInstance;
        // 将element的右上角图标元素存放到refs中，方便后续调用
        componentInstance.loadRefs(element, {
            selectParentComponent: 'single',
            removeComponent: 'single',
            moveComponent: 'single'
        });

        const parent: BuilderHTMLElement = this.getParentElement(element);
        // 注册删除图标点击事件
        if (componentInstance.refs.removeComponent) {
            // 保证组件上挂载的删除图标是自己的，而不是子组件的
            if (componentInstance.refs.removeComponent.getAttribute('componentid') === componentInstance.id) {
                componentInstance.addEventListener(componentInstance.refs.removeComponent, 'click', () =>
                    this.removeComponent(componentInstance, parent));
            } else {
                componentInstance.refs.removeComponent = null;
            }

        }
        // 拖拽图标
        if (componentInstance.refs.moveComponent) {
            // 保证组件上挂载的拖拽图标是自己的，而不是子组件的
            if (componentInstance.refs.moveComponent.getAttribute('componentid') !== componentInstance.id) {
                componentInstance.refs.moveComponent = null;
            }
        }

        // 注册选中父级图标点击事件
        if (componentInstance.refs.selectParentComponent) {
            // 保证组件上挂载的删除图标是自己的，而不是子组件的
            if (componentInstance.refs.selectParentComponent.getAttribute('componentid') === componentInstance.id) {
                componentInstance.addEventListener(componentInstance.refs.selectParentComponent, 'click', (e: PointerEvent) => {
                    e.stopPropagation();
                    e.preventDefault();
                    this.selectParentComponent(componentInstance, e)
                });
            } else {
                componentInstance.refs.selectParentComponent = null;
            }
        }

        // 组件的定制按钮：注册点击事件
        if (componentInstance.customToolbarConfigs) {
            componentInstance.customToolbarConfigs.forEach(btnConfig => {

                componentInstance.loadRefs(element, {
                    [btnConfig.id]: 'single'
                });

                const btnEle = componentInstance.refs[btnConfig.id];
                if (btnEle && btnEle.click) {
                    componentInstance.addEventListener(btnEle, 'click', (e) =>
                        btnConfig.click(e, componentInstance, parent));
                }
            });

        }

        return element;
    }


    /**
     * 初始拖拽
     */
    initDragula(): any {
        const options: any = this.options;
        const self = this;
        if (this.dragula) {
            this.dragula.destroy();
        }

        if (!dragula) {
            return;
        }
        // 运行环境,区分移动端与PC端
        const envType =  this.options.designerHost.getService('FormBasicService').envType

        const formElement = document.body.querySelector('[ref=form]') || document.body;
        // 可拖拽容器在attachComponents 方法中添加
        this.dragula = dragula([], {
            // 镜像容器
            mirrorContainer: formElement,
            direction: 'mixed',
            revertOnSpill: true,
            dragPositon: envType === 'mobileDesigner' ? 'before' : 'after',
            // 判断是否可移动
            moves(el: BuilderHTMLElement, container: HTMLElement, handle: HTMLElement): boolean {
                let moves = true;

                // 包含no-drag样式的元素不允许拖动
                if (el.classList.contains('no-drag')) {
                    moves = false;
                }
                // 为防止误操作，可视化区域的控件只能通过移动图标来拖拽
                if (el.component && el.componentInstance) {
                    moves = handle.classList.contains('f-icon-yxs_move') && !!handle.getAttribute('cmpIcon');
                }

                return moves;
            },
            // 判断是否可拷贝
            copy(el: HTMLElement): boolean {
                // 工具箱里的div需要配置drag-copy
                return el.classList.contains('drag-copy');
            },
            // 判断目标区域是否可接收拖拽的控件
            accepts(el: HTMLElement, target: BuilderHTMLElement, source: HTMLElement): boolean {
                const canAccept = self.checkCanAcceptDrops(el, target, source);
                const guMirrotElement = formElement.lastElementChild;
                if (canAccept) {
                    guMirrotElement.className = guMirrotElement.className.replace('undroppable', '');
                } else if (!guMirrotElement.className.includes('undroppable')) {
                    guMirrotElement.className += ' undroppable';
                }
                return canAccept;
            }
        })
            .on('over', (el, container) => {
                container.className += ' drag-over';
                if (container.componentInstance && container.componentInstance.onDragOver) {
                    container.componentInstance.onDragOver();
                }
            })
            .on('out', (el, container) => {
                container.className = container.className.replace('drag-over', '');
                if (container.componentInstance && container.componentInstance.onDragOut) {
                    container.componentInstance.onDragOut();
                }
            })
            .on('drop', (element, target, source, sibling) => this.onDrop(element, target, source, sibling));
    }

    /**
     * 定位元素的可拖拽的cotainer类父级节点，即为dragref='xxx-container' class='drag-container'的层级节点
     * @param element DOM元素
     */
    getParentElement(element: BuilderHTMLElement): BuilderHTMLElement {
        let container: BuilderHTMLElement = element;
        do {
            container = container.parentNode as BuilderHTMLElement;
        } while (container && !container.componentInstance);
        return container;
    }
    /**
     * 判断是否可以接收拖拽的新控件
     * @param el 拖拽的新控件元素
     * @param target  目标位置
     * @returns boolean
     */
    private checkCanAcceptDrops(el: BuilderHTMLElement, target: BuilderHTMLElement, sourceContainer?: BuilderHTMLElement): boolean {

        if (!!el.contains(target) || target.classList.contains('no-drop')) {
            return false;
        }
        const result = false;
        if (el.componentInstance && el.componentInstance.getDragScopeElement) {
            const dragScopEle = el.componentInstance.getDragScopeElement();
            if (dragScopEle) {
                if (!dragScopEle.contains(target)) {
                    return false;
                }
            }
        }
        if (target.componentInstance && target.componentInstance.canAccepts) {
            return target.componentInstance.canAccepts(el, target, sourceContainer);
        }
        return result;

    }
    /**
     * 删除控件
     * @param targetComponentInstance 要删除的控件实例
     * @param parent 可拖拽的父dom节点
     */
    removeComponent(targetComponentInstance: any, parent: BuilderHTMLElement): any {
        if (!parent) {
            return;
        }
        const componentSchema = targetComponentInstance.component;

        const msgService = this.options.designerHost.getService('MessagerService');

        if (!msgService || !msgService.question) {
            return;
        }
        msgService.question('确定删除控件？', () => {

            let parentComponentInstance = parent.componentInstance;
            let locatePredicate: any = { id: componentSchema.id };

            if (componentSchema.type === 'Component') {
                // 组件级节点使用component属性定位
                locatePredicate = { component: componentSchema.id };
            }
            const index = findIndex(parent.childrenContents, locatePredicate);
            if (index !== -1) {
                // const path: string = this.getComponentsPath();

                if (parent.childrenContents) {
                    parent.childrenContents.splice(index, 1);
                }
                // 控件的移除回调方法
                if (targetComponentInstance.onRemoveComponent) {
                    const result = targetComponentInstance.onRemoveComponent();
                    if (result && result.parentComponentInstance) {
                        parentComponentInstance = result.parentComponentInstance;
                    }
                }

                // 触发页面重刷,parent.componentInstance是被删组件的父容器实例
                const rebuild = parentComponentInstance.rebuild() || Promise.resolve();
                rebuild.then(() => {
                    this.emit('removeComponent');
                    // this.emit('change', this.form);
                });
            }
        });
    }
    /**
     * 选中父级事件
     * @param targetComponentInstance 要选中的父级控件实例
     */
    selectParentComponent(targetComponentInstance: any, e: PointerEvent) {
        this.getParent(targetComponentInstance).triggerComponentClick(e)
    }
    /**
     * 获取父级控件实例 (过滤ComponentRef)
     * @param targetComponentInstance 当前控件实例
     * @return parentComponentInstance 父级控件实例
     */
    getParent(targetComponentInstance: any) {
        const parent = targetComponentInstance.parent
        if (parent.component.type === "ComponentRef") {
            return this.getParent(parent)
        }
        return parent
    }

    /**
     * 拖拽结束
     * @param element 拖拽的元素
     * @param target 目标容器元素
     * @param source 原容器元素
     * @param sibling 目标位置的下一个同级元素
     */
    onDrop(element: BuilderHTMLElement, target: BuilderHTMLElement, source: BuilderHTMLElement, sibling: BuilderHTMLElement) {
        if (!target) {
            return;
        }
        // If you try to drop within itself.
        if (element.contains(target)) {
            return;
        }
        const sourceType = element.getAttribute('data-sourceType');

        switch (sourceType) {
            case 'control': case 'field': case 'entity': {
                this.createControlFromOutside(element, target, source, sibling);
                break;
            }
            default: {
                if (source.childrenContents) {
                    this.dragBetweenCurrentForm(element, target, source, sibling);

                } else {
                    // 移除拷贝生成的源DOM
                    if (target.contains(element)) {
                        target.removeChild(element);
                    }
                }
            }
        }

    }
    /**
     * 在现有的表单中拖拽移动控件位置
     * @param element 拖拽的元素
     * @param target 目标容器元素
     * @param source 源容器元素
     * @param sibling 目标位置的下一个同级元素
     */
    private dragBetweenCurrentForm(element: BuilderHTMLElement, target: BuilderHTMLElement, source: BuilderHTMLElement, sibling: BuilderHTMLElement) {
        let sourceControlSchema;
        let index = -1;
        // Form、DataGrid等控件在拖拽时，需要连同所属Component一起拖拽。
        if (element.componentInstance && element.componentInstance.triggerBelongedComponentToMoveWhenMoved) {
            const cmpInstance = element.componentInstance.getBelongedComponentInstance(element.componentInstance);
            if (cmpInstance) {
                // 将拖拽元素替换为所属Component
                element = cmpInstance.element;
                // 将源容器元素替换为所属Component的父级元素
                source = cmpInstance.element.parentElement;
            }

        }
        const elementComponentSchema = element.componentInstance && element.componentInstance.component;

        let locatePredicate: any = { id: elementComponentSchema && elementComponentSchema.id };
        if (elementComponentSchema && elementComponentSchema.type === 'Component') {
            // 组件级节点使用component属性定位
            locatePredicate = { component: elementComponentSchema.id };
        }
        index = findIndex(source.childrenContents, locatePredicate);

        if (index !== -1) {
            // 从源容器schema json中移除
            sourceControlSchema = source.childrenContents.splice(index, 1);

            sourceControlSchema = sourceControlSchema[0];
        }

        this.addNewControlToTarget(target, sourceControlSchema, sibling);

        // 源容器的控件被移除掉
        if (source.componentInstance && source.componentInstance.onChildElementMovedOut) {
            source.componentInstance.onChildElementMovedOut(element);
        }

        // 目标容器接收新控件
        if (target.componentInstance && target.componentInstance.onAcceptMovedChildElement) {
            target.componentInstance.onAcceptMovedChildElement(element, source);
        }

        if (!sourceControlSchema) {
            return;
        }

        // 触发容器的刷新
        let rebuild;
        let movedInSameContainer = false;
        if (target !== source) {
            if (source.childrenContents && source.contains(target)) {
                rebuild = source.componentInstance.rebuild();
            } else if (target.contains(source)) {
                rebuild = target.componentInstance.rebuild();
            } else {
                if (source.childrenContents) {
                    rebuild = source.componentInstance.rebuild();
                }
                rebuild = target.componentInstance.rebuild();
            }
        } else {
            movedInSameContainer = true;
            // 在同一个容器中移动控件，只需要刷新容器本身即可。
            rebuild = target.componentInstance.rebuild();
        }

        if (!rebuild) {
            rebuild = Promise.resolve();
        }

        return rebuild.then(() => {
            this.emit('change', this.form);
        });
    }

    /**
     * 从控件工具箱中拖拽新建控件
     * @param element 拖拽的元素
     * @param target 目标容器元素
     * @param source 原容器元素
     * @param sibling 目标位置的下一个同级元素
     */
    private createControlFromOutside(element: BuilderHTMLElement, target: BuilderHTMLElement, source: BuilderHTMLElement, sibling: BuilderHTMLElement) {

        if (target.componentInstance.onAcceptNewChildElement) {
            const targetPosition = this.getNewControlTargetPosition(target, sibling);
            target.componentInstance.onAcceptNewChildElement(element, targetPosition).subscribe(componentResolveContext => {
                if (!componentResolveContext) {
                    return;
                }

                // sourceControlSchema为空代表了DOM插入已经处理，无需更新
                const sourceControlSchema = componentResolveContext.componentSchema;
                if (sourceControlSchema) {
                    this.addNewControlToTarget(target, sourceControlSchema, sibling);
                }

                // 执行新增元素的移动后事件
                if (target.componentInstance && target.componentInstance.afterAcceptNewChildElement) {
                    target.componentInstance.afterAcceptNewChildElement(componentResolveContext);
                }

                let rebuild = target.componentInstance.rebuild();
                if (!rebuild) {
                    rebuild = Promise.resolve();
                }

                return rebuild.then(() => {
                    this.emit('change', this.form, componentResolveContext);
                });
            });
        }

        // 移除拷贝生成的源DOM
        if (target.contains(element)) {
            target.removeChild(element);
        }
    }
    /**
     * 将新控件json添加到新容器schema json中
     * @param element 拖拽的元素
     * @param sourceControlSchema 新控件的JSON schema结构
     * @param sibling 目标位置的下一个同级元素
     */
    private addNewControlToTarget(target: BuilderHTMLElement, sourceControlSchema: ComponentSchema, sibling: BuilderHTMLElement) {
        const parent = target.componentInstance;
        let index;
        if (!sourceControlSchema) {
            return;
        }
        if (target.childrenContents) {
            if (target.componentInstance.onAddNewChildElement) {
                const position = target.componentInstance.onAddNewChildElement(sourceControlSchema, sibling)
                if (position >= 0) {
                    target.childrenContents.splice(position, 0, sourceControlSchema);
                    return position
                }
            }
            if (sibling) {
                if (!sibling.getAttribute('data-noattach')) {
                    // 定位目标位置
                    const siblingComponentSchema = sibling.componentInstance.component;
                    let locatePredicate: any = { id: siblingComponentSchema.id };
                    if (siblingComponentSchema.type === 'Component') {
                        locatePredicate = { component: siblingComponentSchema.id };
                    }

                    index = findIndex(target.childrenContents, locatePredicate);
                    index = (index === -1) ? 0 : index;
                } else {
                    index = sibling.getAttribute('data-position');
                }
                if (index !== -1) {
                    target.childrenContents.splice(index, 0, sourceControlSchema);
                }
            } else {
                target.childrenContents.push(sourceControlSchema);
            }

        }
        return index;
    }

    /**
     * 获取新控件的目标位置
     */
    private getNewControlTargetPosition(target: BuilderHTMLElement, sibling: BuilderHTMLElement) {

        // 不允许放置
        if (!target.childrenContents) {
            return -1;
        }

        // 空容器：放置第1个位置
        if (target.childrenContents.length === 0) {
            return 0;
        }

        // 后面没有兄弟控件：放置到最后
        if (!sibling) {
            return target.childrenContents.length;
        }

        // noattach???
        if (sibling.getAttribute('data-noattach')) {
            return sibling.getAttribute('data-position');
        }

        const siblingComponentSchema = sibling.componentInstance.component;
        let locatePredicate;
        if (siblingComponentSchema.type === 'Component') {
            locatePredicate = { component: siblingComponentSchema.id };
        } else {
            locatePredicate = { id: siblingComponentSchema.id };
        }
        let position = findIndex(target.childrenContents, locatePredicate);
        position = (position === -1) ? 0 : position;

        return position;
    }

    /**
     * 获取当前节点在父容器json中的循环嵌套关键字，默认为“contents”
     */
    getComponentsPath(): string {
        return 'contents';
    }


    destroy() {
        if (this.webform.initialized) {
            this.webform.destroy();
        }
        super.destroy();
    }


    clear() {
        if (this.webform.initialized) {
            this.webform.clear();
        }
    }
    detach() {
        if (this.dragula) {
            this.dragula.destroy();
        }
        this.dragula = null;

        super.detach();
    }

    get container() {
        return this.webform.form.components;
    }

    /**
     * 获取表单全部的组件实例，平铺结构。用于外部监听各组件的事件
     */
    getAllComponents() {
        const allCmps = [];
        this.getChildComponents(this.webform.components, allCmps);
        return allCmps;
    }

    private getChildComponents(components: FarrisDesignBaseNestedComponent[], allCmps: FarrisDesignBaseComponent[]) {
        if (!components) {
            return allCmps;
        }
        for (const cmp of components) {
            if (cmp.components && cmp.components.length) {
                this.getChildComponents(cmp.components, allCmps);
            }
            allCmps.push(cmp);
        }
    }

    getAllUiTemplates() {
        return Templates.templates;
    }
}
